/* ----------------------------------------------------------------------------
* File: sockets.cpp
*
* Desc: Common stuffs for sockets
*
* Created: 11/03/2011, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include "common.h"
#include "utils.h"


/************************************************************************/
/*	Desc: Create TCP socket                                                   
	Params:
		port - communication port
		log_print - pointer to a log function							
	Return:
		socket descriptor, -1 if error									*/
/************************************************************************/
int UtilsTCPSocket(unsigned short port, int *status, FILE *logFile)
{
	int sock = 0;
	struct sockaddr_in sock_addr;

	// Generate a network address for the socket
	memset(&sock_addr, 0, sizeof(sock_addr));
	sock_addr.sin_family = AF_INET;
	sock_addr.sin_addr.s_addr = INADDR_ANY;
	sock_addr.sin_port = htons(port);

	// Create a new socket
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sock < 0)
	{	
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsTCPSocketCreate: Unable to create socket, %s\n", strerror(errno));
			fflush(logFile);
		}
		return sock;
	}

	// Set option SO_REUSEADDR for socket
	// It's allows to ignore TIME_WAIT behavior of TCP
	const int on = 1;
	if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, (int)sizeof(on)) < 0)
	{
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsTCPSocketCreate: Unable to set option SO_REUSEADDR, %s\n", strerror(errno));
			fflush(logFile);
		}
		close(sock);
		return UTILS_ERROR;
	}

	// Bind a new socket
	if (bind(sock, (struct sockaddr*)&sock_addr, sizeof(sock_addr)) < 0)
	{
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsTCPSocketCreate: Unable to bind socket, %s\n", strerror(errno));
			fflush(logFile);
		}
		close(sock);
		return UTILS_ERROR;
	}

	return sock;
}


/************************************************************************/
/*	Desc: Create UDP socket                                                   
	Params:
		port - communication port
		log_print - pointer to a log function							
	Return:
		socket descriptor, -1 if error									*/
/************************************************************************/
int UtilsUDPSocket(unsigned short port, int *status, FILE *logFile)
{
	return UTILS_ERROR;
}


/************************************************************************/
/*	Desc: Listen socket                                                   
	Params:
		sock - socket descriptor
		max_conn - maximum accepted connections
		log_print - pointer to a log function							
	Return:
		UTILS_OK if successful, UTILS_ERROR if error					*/
/************************************************************************/
int UtilsSocketListen(int sock, unsigned int max_conn, int *status, FILE *logFile)
{
	// listen socket
	if (listen(sock, max_conn) < 0)
	{
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsSocketListen: Unable to listen socket, sock = %d, %s\n", sock, strerror(errno));
			fflush(logFile);
		}
		return UTILS_ERROR;
	}
	return UTILS_OK;
}


/************************************************************************/
/*	Desc: Accept connection by socket                                                   
	Params:
		sock - socket descriptor
		timeout - time for select waiting
		log_print - pointer to a log function							
	Return:
		accepted socket descriptor, -1 if error							*/
/************************************************************************/
int UtilsSocketAccept(int sock, int timeout, struct sockaddr_in *remote_address, int *status, FILE *logFile)
{
	int accepted_sock;
	int rv = 0;
	fd_set rfds;
	struct timeval tv;
	size_t addrlen;

	// select socket
	FD_ZERO(&rfds);
	FD_SET(sock, &rfds);
    if (timeout != WAIT_FOREVER)
    {
        tv.tv_sec = timeout / 1000;							// seconds
		tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;	// microseconds
	    rv = select(sock + 1, &rfds, NULL, NULL, &tv);
    }
    else
    {
		// wait forever
        rv = select(sock + 1, &rfds, NULL, NULL, NULL);
    }

	if (rv == 0)	// timeout
	{
		//if (log_print != NULL)
		//	log_print("UtilsSocketAccept: select timeout, sock = %d\n", sock);
		return UTILS_TIMEOUT;
	}
	else if (rv < 0)	// error
	{
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsSocketAccept: select error, sock = %d, %s\n", sock, strerror(errno));
			fflush(logFile);
		}
		return UTILS_ERROR;
	}

	// accept socket
    addrlen = sizeof(struct sockaddr_in);
    accepted_sock = accept(sock, (struct sockaddr*)remote_address, &addrlen);
    if (accepted_sock <= 0)
    {
		*status = errno;
		if (errno != EWOULDBLOCK)	// socket is non-blocking mode
		{
			if (logFile != NULL)
			{
				fprintf(logFile, "UtilsSocketAccept: accept failed, sock = %d, %s\n", sock, strerror(errno));
				fflush(logFile);
			}
			return UTILS_ERROR;
		}
    }

	return accepted_sock;
}


/************************************************************************/
/*	Desc: Connect to remote host by socket                                                   
	Params:
		sock - socket descriptor
		addr - string containing remote address
		port - remote port for connection
		timeout - time for select waiting
		log_print - pointer to a log function							
	Return:
		0 if successful, -1 if error									*/
/************************************************************************/
int UtilsSocketConnect(int sock, const char* addr, int port, int timeout, int *status, FILE *logFile)
{
	fd_set rfds;
	int rv = 0;
	struct timeval tv;
	struct sockaddr_in sock_addr;

	// select socket
	FD_ZERO(&rfds);
	FD_SET(sock, &rfds);
    if (timeout != WAIT_FOREVER)
    {
        tv.tv_sec = timeout / 1000;							// seconds
		tv.tv_usec = (timeout - tv.tv_sec * 1000) * 1000;	// microseconds
	    rv = select(sock + 1, NULL, &rfds, NULL, &tv);
    }
    else
    {
        // wait forever
        rv = select(sock + 1, NULL, &rfds, NULL, NULL);
    }

	if (rv == 0)	// timeout
	{
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsSocketConnect: select timeout, sock = %d\n", sock);
			fflush(logFile);
		}
		return UTILS_TIMEOUT;
	}
	else if (rv < 0)	// error
	{
		*status = errno;
		if (logFile != NULL)
		{
			fprintf(logFile, "UtilsSocketConnect: select error, sock = %d, %s\n", sock, strerror(errno));
			fflush(logFile);
		}
		return UTILS_ERROR;
	}

	// generate remote address
	memset(&sock_addr, 0, sizeof(sock_addr));
	sock_addr.sin_family = AF_INET;
	sock_addr.sin_port = htons(port);
	sock_addr.sin_addr.s_addr = inet_addr(addr);
	if (sock_addr.sin_addr.s_addr == INADDR_NONE)
	{
		struct hostent *host;
		if ((host = gethostbyname(addr)) != NULL)
			memcpy((void*)sock_addr.sin_addr.s_addr, (void*)host->h_addr, sizeof(unsigned long));
		else
		{
			*status = errno;
			if (logFile != NULL)
			{
				fprintf(logFile, "UtilsSocketConnect: unable to get host address, %s\n", strerror(errno));
				fflush(logFile);
			}
			return UTILS_ERROR;
		}
	}

	// connect socket
    if(connect(sock,(struct sockaddr*)&sock_addr, (int) sizeof(sock_addr)) < 0)
    { 
		*status = errno;
		if (errno != EWOULDBLOCK)	// socket is non-blocking mode
		{
			if (logFile != NULL)
			{
				fprintf(logFile, "UtilsSocketConnect: connect failed, sock = %d, %s\n", sock, strerror(errno));
				fflush(logFile);
			}
			return UTILS_ERROR;
		}
    }

    return UTILS_OK;
}